你再不知道分布式事务,我就真的生气了!
The following article is from 捡田螺的小男孩 Author 捡田螺的小男孩
最近看了几篇有关于分布式事务的博文,做了一下笔记,并总结出这篇文章。
数据库事务
数据库事务(简称:事务),是数据库管理系统执行过程中的一个逻辑单位,由一个有限的数据库操作序列构成。
这些操作要么全部执行,要么全部不执行,是一个不可分割的工作单位。
原子性(Atomicity)
一致性(Consistency)
隔离性(Isolation)
持久性(Durabilily)
简称就是 ACID:
原子性:事务作为一个整体被执行,包含在其中的对数据库的操作要么全部被执行,要么都不执行。
一致性:指在事务开始之前和事务结束以后,数据不会被破坏,假如 A 账户给 B 账户转 10 块钱,不管成功与否,A 和 B 的总金额是不变的。
隔离性:多个事务并发访问时,事务之间是相互隔离的,即一个事务不影响其它事务运行效果。简言之,就是事务之间是进水不犯河水的。
持久性:表示事务完成以后,该事务对数据库所作的操作更改,将持久地保存在数据库之中。
事务的实现原理
本地事务
事务日志
redo log(重做日志):通常是物理日志,记录的是数据页的物理修改,而不是某一行或某几行修改成怎样,它用来恢复提交后的物理数据页。
undo log(回滚日志):是逻辑日志,和 redo log 记录物理日志的不一样。
可以这样认为,当 delete 一条记录时,undo log 中会记录一条对应的 insert 记录,当 update 一条记录时,它记录一条对应相反的 update 记录。
事务 ACID 特性的实现思想:
原子性:是使用 undo log 来实现的,如果事务执行过程中出错或者用户执行了 rollback,系统通过 undo log 日志返回事务开始的状态。
持久性:使用 redo log 来实现,只要 redo log 日志持久化了,当系统崩溃,即可通过 redo log 把数据恢复。
隔离性:通过锁以及 MVCC,使事务相互隔离开。
一致性:通过回滚、恢复,以及并发情况下的隔离性,从而实现一致性。
分布式事务
微服务架构下的分布式事务
分库分表下的分布式事务
比如 A 转 10 块给 B,A 的账户数据是在北京机房,B 的账户数据是在深圳机房。
CAP 理论&BASE 理论
CAP 理论
BASE 理论
分布式事务的几种解决方案
2PC(二阶段提交)方案
TCC(Try、Confirm、Cancel)
本地消息表
最大努力通知
Saga 事务
二阶段提交方案
二阶段提交成功的情况:
准备阶段,事务管理器向每个资源管理器发送准备消息,如果资源管理器的本地事务操作执行成功,则返回成功。
提交执行阶段,如果事务管理器收到了所有资源管理器回复的成功消息,则向每个资源管理器发送提交消息,RM 根据 TM 的指令执行提交。
如图:
二阶段提交失败的情况:
准备阶段,事务管理器向每个资源管理器发送准备消息,如果资源管理器的本地事务操作执行成功,则返回成功,如果执行失败,则返回失败。
提交执行阶段,如果事务管理器收到了任何一个资源管理器失败的消息,则向每个资源管理器发送回滚消息。
单点问题:如果事务管理器出现故障,资源管理器将一直处于锁定状态。
性能问题:所有资源管理器在事务提交阶段处于同步阻塞状态,占用系统资源,一直到提交完成,才释放资源,容易导致性能瓶颈。
数据一致性问题:如果有的资源管理器收到提交的消息,有的没收到,那么会导致数据不一致问题。
TCC(补偿机制)
Try 阶段:尝试去执行,完成所有业务的一致性检查,预留必须的业务资源。
Confirm 阶段:该阶段对业务进行确认提交,不做任何检查,因为 Try 阶段已经检查过了,默认 Confirm 阶段是不会出错的。
Cancel 阶段:若业务执行失败,则进入该阶段,它会释放 Try 阶段占用的所有业务资源,并回滚 Confirm 阶段执行的所有操作。
主业务服务:主业务服务负责发起并完成整个业务活动。
从业务服务:从业务服务是整个业务活动的参与方,实现 Try、Confirm、Cancel 操作,供主业务服务调用。
业务活动管理器:业务活动管理器管理控制整个业务活动,包括记录事务状态,调用从业务服务的 Confirm 操作,调用从业务服务的 Cancel 操作等。
生成一条订单记录,订单状态为待确认。
将用户 A 的账户金币中余额更新为 90,冻结金币为 10(预留业务资源)。
将用户的礼物数量为 5,预增加数量为 10。
Try 成功之后,便进入 Confirm 阶段。
Try 过程发生任何异常,均进入 Cancel 阶段。
订单状态更新为已支付。
更新用户余额为 90,可冻结为 0。
用户礼物数量更新为 15,预增加为 0。
Confirm 过程发生任何异常,均进入 Cancel 阶段。
Confirm 过程执行成功,则该事务结束。
修改订单状态为已取消。
更新用户余额回 100。
更新用户礼物数量为 5。
应用侵入性强,Try、Confirm、Cancel 三个阶段都需要业务逻辑实现。
需要根据网络、系统故障等不同失败原因实现不同的回滚策略,实现难度大,一般借助 TCC 开源框架,ByteTCC,TCC-transaction,Himly。
本地消息表
可以看一下基本的实现流程图:
基本实现思路如下。
需要有一个消息表,记录着消息状态相关信息。
业务数据和消息表在同一个数据库,即要保证它俩在同一个本地事务。
在本地事务中处理完业务数据和写消息表操作后,通过写消息到 MQ 消息队列。
消息会发到消息消费方,如果发送失败,即进行重试。
处理消息队列中的消息,完成自己的业务逻辑。
此时如果本地事务处理成功,则表明已经处理成功了。
如果本地事务处理失败,那么就会重试执行。
如果是业务上面的失败,给消息生产方发送一个业务补偿消息,通知进行回滚等操作。
优缺点:该方案的优点是很好地解决了分布式事务问题,实现了最终一致性。缺点是消息表会耦合到业务系统中。
最大努力通知
什么是最大通知?最大努力通知也是一种分布式事务解决方案。
下面是企业网银转账一个例子:
企业网银系统调用前置接口,跳转到转账页。
企业网银调用转账系统接口。
转账系统完成转账处理,向企业网银系统发起转账结果通知,若通知失败,则转账系统按策略进行重复通知。
企业网银系统未接收到通知,会主动调用转账系统的接口查询转账结果。
转账系统会遇到退汇等情况,会定时回来对账。
最大努力通知实现机制如下:
最大努力通知解决方案:要实现最大努力通知,可以采用 MQ 的 ACK 机制。
发起方将通知发给 MQ。
接收通知方监听 MQ 消息。
接收通知方收到消息后,处理完业务,回应 ACK。
接收通知方若没有回应 ACK,则 MQ 会间隔 1min、5min、10min 等重复通知。
接受通知方可用消息校对接口,保证消息的一致性。
转账业务实现流程图:
用户请求转账系统进行转账。
转账系统完成转账,将转账结果发给 MQ。
企业网银系统监听 MQ,接收转账结果通知,如果接收不到消息,MQ 会重复发送通知。接收到转账结果,更新转账状态。
企业网银系统也可以主动查询转账系统的转账结果查询接口,更新转账状态。
Saga 事务
Saga = Long Live Transaction(LLT,长活事务)。
LLT = T1 + T2 + T3 + ... + Ti(Ti 为本地短事务)。
每个本地事务 Ti 有对应的补偿 Ci。
正常情况:T1 T2 T3 ... Tn
异常情况:T1 T2 T3 C3 C2 C1
向后恢复,如果任意本地子事务失败,补偿已完成的事务。如异常情况的执行顺序 T1 T2 Ti Ci C2 C1。
向前恢复,即重试失败的事务,假设最后每个子事务都会成功。执行顺序:T1,T2,...,Tj(失败),Tj(重试),...,Tn。
T1=下订单
T2=扣用户 10 块钱
T3=用户加 10 朵玫瑰
T4=库存减 10 朵玫瑰
C1=取消订单
C2=给用户加 10 块钱
C3=用户减 10 朵玫瑰
C4=库存加 10 朵玫瑰
在应⽤层⾯加⼊逻辑锁的逻辑。
Session 层⾯隔离来保证串⾏化操作。
业务层⾯采⽤预先冻结资⾦的⽅式隔离此部分资⾦。
业务操作过程中通过及时读取当前状态的⽅式获取更新。
参考与感谢:
干货 | 一篇文章带你学习分布式事务
再有人问你分布式事务,把这篇扔给他
聊聊分布式事务,再说说解决方案
MySQL事务实现原理
详细分析 MySQL 事务日志(redo log 和 undo log)
《Saga 分布式事务解决⽅案与实践》
分布式事务解决方案之最大努力通知
作者:Jay_huaxiao
编辑:陶家龙
出处:转载自微信公众号捡田螺的小男孩
精彩文章推荐: